﻿using Microsoft.AspNetCore.Mvc;
using RabbitMQ.Client;
using System.Text;
using System.Threading.Channels;

namespace JyApp.Controllers
{
    /// <summary>
    /// RabbitMQ 从信息接收者角度可以看做三种模式,一对一,一对多(此一对多并不是发布订阅，而是每条信息只有一个接收者)和发布订阅。
    /// 其中一对一是简单队列模式，一对多是Worker模式，
    /// 而发布订阅包括发布订阅模式，路由模式和通配符模式，
    /// 为什么说发布订阅模式包含三种模式呢，其实发布订阅，路由，通配符三种模式都是使用只是交换机(Exchange)类型不一致
    /// </summary>
    [ApiController]
    [Route("[controller]/[action]")]
    public class RabbitMqController : ControllerBase
    {
        private readonly ILogger<RabbitMqController> _logger;
        private readonly IConnection _connection;

        public RabbitMqController(ILogger<RabbitMqController> logger, IConnection connection)
        {
            _logger = logger;
            _connection = connection;
        }

        /// <summary>
        /// 工作队列模式：一对一，一对多(此一对多并不是发布订阅，而是每条信息只有一个接收者)
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public void WorkQueues()
        {
            //_logger.LogInformation("这是一条信息日志"); // 普通信息 
            //_logger.LogWarning("这是一条警告日志");     // 警告 
            //_logger.LogError("这是一条错误日志");       // 错误 
            //_logger.LogDebug("这是一条调试日志");       // 调试（需配置LogLevel）

            //var factory = new ConnectionFactory()       // 创建连接工厂对象
            //{
            //    HostName = "localhost",
            //    Port = 5672,
            //    UserName = "guest",
            //    Password = "guest"
            //};
            //using var connection = factory.CreateConnection();    // 创建连接对象

            using var channel = _connection.CreateModel();         // 创建连接会话对象

            string queueName = "我是创建的队列名称1";

            // 声明一个队列
            channel.QueueDeclare(
                queue: queueName,   // 队列名称
                durable: false,     // 是否持久化，true持久化，队列会保存磁盘，服务器重启时可以保证不丢失相关信息
                exclusive: false,   // 是否排他，如果一个队列声明为排他队列，该队列仅对声明它的连接可见，并在连接断开时自动删除
                autoDelete: true,   // 是否自动删除，自动删除的前提是：至少有一个消费者连接到这个队列，之后所有与这个队列连接的消费者都断开时，才会自动删除
                arguments: null     // 设置队列的其他参数
            );

            //设置rabbit每次只能向消费者发送一条消息，在消费者未确认之前，不再向他发送消息
            channel.BasicQos(
                prefetchSize: 0,    // 消息大小限制（字节），0 表示不限制 
                prefetchCount: 1,   // 预取消息数量 
                global: false       // 是否全局应用（针对整个连接或当前信道）
            );

            //产生发送消息
            for (int i = 0; i < 10; i++)
            {
                string msg = $"我是消息{i + 1}";
                // 消息内容
                byte[] body = Encoding.UTF8.GetBytes(msg);

                // 发送消息=》队列
                channel.BasicPublish("", queueName, null, body);

                _logger.LogInformation($"已发送：{msg}"); // 普通信息 

            }

        }

        /// <summary>
        /// 发布订阅模式（Fanout）
        /// 代码与上面没有什么差异，只是由上面的消息队列声明变成了交换机声明(交换机类型为fanout)，也就说发送者发送消息从原来的直接发送消息队列变成了发送到交换机
        /// </summary>
        [HttpGet]
        public void FanoutMode()
        {
            var channel = _connection.CreateModel();
            
            //定义交换机
            string exchangeName = "我是发布订阅Fanout交换机";
            channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout, autoDelete: true);// 把交换机设置为 fanout 发布订阅模式

            //产生发送消息
            for (int i = 0; i < 10; i++)
            {
                string msg = $"我是消息{i + 1}";
                // 消息内容
                byte[] body = Encoding.UTF8.GetBytes(msg);

                // 发送消息=》交换机
                channel.BasicPublish(exchangeName, "", null, body);

                _logger.LogInformation($"已发送：{msg}"); // 普通信息 

            }

        }

        /// <summary>
        /// 路由模式
        /// 路由模式下,在发布消息时指定不同的routeKey,交换机会根据不同的routeKey分发消息到不同的队列中
        /// </summary>
        [HttpGet]
        public void DirectMode()
        {
            var channel = _connection.CreateModel();

            //声明交换机
            string exchangeName = "我是路由模式Direct交换机";
            channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, autoDelete: true);

            //要发送的RountingKey
            string RountingKey = "jying3";

            //产生发送消息
            for (int i = 0; i < 10; i++)
            {
                string msg = $"我是消息{i + 1}";
                // 消息内容
                byte[] body = Encoding.UTF8.GetBytes(msg);

                // 发送消息=》交换机
                channel.BasicPublish(exchangeName, RountingKey, null, body);

                _logger.LogInformation($"已发送：{msg}"); // 普通信息 

            }

        }

        /// <summary>
        /// 通配符模式
        /// 路由键由多个单词组成，用点号.分隔（例如：weather.beijing.alert ）。
        /// 通配符用于匹配这些单词：
        /// *（星号）：匹配一个单词（如 weather.* 可匹配 weather.beijing ，但不能匹配 weather.beijing.alert ）。
        /// #（井号）：匹配零个或多个单词（如 weather.# 可匹配 weather、weather.beijing 或 weather.beijing.alert ）。
        /// </summary>
        [HttpGet]
        public void TopicMode(string key)
        {
            var channel = _connection.CreateModel();

            //声明交换机
            string exchangeName = "我是通配符模式Topic交换机";
            channel.ExchangeDeclare(exchangeName, ExchangeType.Topic, autoDelete: true);

            //要发送的RountingKey
            //string RountingKey = "jying.66";

            //产生发送消息
            for (int i = 0; i < 10; i++)
            {
                string msg = $"我是消息{i + 1}";
                // 消息内容
                byte[] body = Encoding.UTF8.GetBytes(msg);

                // 发送消息=》交换机
                channel.BasicPublish(exchangeName, key, null, body);

                _logger.LogInformation($"已发送：{msg}"); // 普通信息 

            }

        }



    }
}
